import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import javax.imageio.ImageIO;
import javax.vecmath.Vector3f;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.util.glu.GLU;

import org.newdawn.slick.opengl.*;

import com.bulletphysics.dynamics.RigidBody;
import com.bulletphysics.collision.broadphase.*;
import com.bulletphysics.collision.dispatch.*;
import com.bulletphysics.collision.shapes.*;
import com.bulletphysics.dynamics.DiscreteDynamicsWorld;
import com.bulletphysics.dynamics.RigidBody;
import com.bulletphysics.dynamics.RigidBodyConstructionInfo;
import com.bulletphysics.dynamics.constraintsolver.*;
import com.bulletphysics.linearmath.*;

public class Md2 {
	
	protected RandomAccessFile in;
	protected byte b[] = new byte[4];
	protected MD2Header Head;
	protected MD2Frame[] frame;
	protected MD2Vtx[] vtx;
	protected MD2Face[] face;
	protected Mesh_UV[] UV;
	protected MD2TexCoord[] MD2_UV;
	protected MD2FrameInfo[] FrameInfo;
	protected String textureName;
	protected int nFrames,nTri,nVtx,nUV;
	protected long ItemLoop;
	protected int TexID; //this should be type GLuint
	Thread footstepsSound=new Sound("footsteps.wav",10);
	int counter=0;
	//var used in rendering
	protected long Time1, NextFrame;
	protected int CurrFrame = 0;
	protected final int FRAME_DELAY = 40000000;	//delay between frames in model animation in nanoseconds
	protected float m_nX, m_nY, m_nZ;       //position
	protected float m_rX=0, m_rY=0, m_rZ=0;       //rotation
	protected float m_sX=2.0f, m_sY=2.0f, m_sZ=2.0f; 		//scaling
	protected float o_width=17.0f, o_height=17.0f, o_length=17.0f;	//dimension of object for object box
	protected  Physics physics_model;
	protected RigidBody model;
	protected Vector3f currentVelocity = new Vector3f();
	
	public Md2(Vector3f position){
		face = null;
		frame =null;
		UV = null;
		Head = new MD2Header();
		TexID = 0;
		
	    Time1 = System.nanoTime();
	    NextFrame = Time1 + FRAME_DELAY;
	    Vector3f size;
	    m_nX = position.x;
	    m_nY = position.y;
	    m_nZ = position.z;
	    
	    size=new Vector3f(o_width*m_sX, o_height*m_sY, o_length*m_sZ);	//size of object - change this parameters to change its size
	    physics_model = BSP.get_physicModel();	//get model,that we can operate with physics
	    model = physics_model.addDynamicObject(size, position);	    //add model in physics engine
	}
	
	private void LoadHeader(){
		try{
			for (int i=0;i<4;i++)
				Head.ID[i]=(int)in.read();
			
			Head.Version = readInt();
			Head.TexWidth = readInt();
			Head.TexHeight = readInt();
			Head.FrameSize = readInt();
			Head.nTextures = readInt();
			Head.nVertices = readInt();
			Head.nTexCoords = readInt();
			Head.nTriangles = readInt();
			Head.nGLCmd = readInt();
			Head.nFrames = readInt();
			Head.TexOffset = readInt();
			Head.UVOffset = readInt();
			Head.FaceOffset = readInt();
			Head.FrameOffset = readInt();	
			Head.GLCmdOffset = readInt();
			Head.EOFOffset = readInt();
		}catch (IOException e){
			System.out.println("Error reading md2 file.");
		}
		
		nFrames=Head.nFrames;
		nTri=Head.nTriangles;
		nVtx=Head.nVertices;
		nUV=Head.nTexCoords;
		
	}
	
	private void LoadFrames(){
		try{
			frame = new MD2Frame[nFrames];	//array is the size of nFrames that we get form header
			for(int FrameLoop = 0; FrameLoop != nFrames; ++FrameLoop){
				frame[FrameLoop] = new MD2Frame();
				frame[FrameLoop].Vtx = new Mesh_Vtx[nVtx];
				frame[FrameLoop].Norm = new Mesh_Vtx[nVtx];
			}
			vtx = new MD2Vtx[Head.nVertices];
			face = new MD2Face[Head.nTriangles];
			UV = new Mesh_UV[Head.nTriangles];
			MD2_UV = new MD2TexCoord[Head.nTexCoords];
			
			//read first texture name
			int[] texName= new int[64];
			if(Head.nTextures>0){
				in.seek(Head.TexOffset);
				textureName="";
				for(int i=0;i<64;i++){
					texName[i]= (int)in.read();
					textureName+=(char)texName[i];
				}
			}
			
			//read face data
			in.seek(Head.FaceOffset);
			for(int i = 0; i<Head.nTriangles; i++){
				face[i] = new MD2Face();
				face[i].p1 = readShort();
				face[i].p2 = readShort();
				face[i].p3 = readShort();
				face[i].uv1 = readShort();
				face[i].uv2 = readShort();
				face[i].uv3 = readShort();
			}
			
			//read UV data
			in.seek(Head.UVOffset);
			for(int i = 0; i<Head.nTexCoords; i++){
				MD2_UV[i] = new MD2TexCoord();
				MD2_UV[i].u = readShort();
				MD2_UV[i].v = readShort();
			}

			// Convert into regular UVs
			for(int i = 0; i!=nUV; ++i){
				UV[i] = new Mesh_UV();
				UV[i].u=((float)MD2_UV[i].u)/Head.TexWidth;
				UV[i].v=((float)MD2_UV[i].v)/Head.TexHeight;
			 }
			
			 // Load frame vertex info
			FrameInfo = new MD2FrameInfo[nFrames];
			for(int i = 0; i != nFrames; ++i){
				 // Get frame conversion data
				in.seek(Head.FrameOffset + (Head.FrameSize * i));
				FrameInfo[i] = new MD2FrameInfo();
				FrameInfo[i].Scale[0] = readFloat();
				FrameInfo[i].Scale[1] = readFloat();
				FrameInfo[i].Scale[2] = readFloat();
				
				FrameInfo[i].Translate[0] = readFloat();
				FrameInfo[i].Translate[1] = readFloat();
				FrameInfo[i].Translate[2] = readFloat();
				
				//read frame info (frame name)
				int c;
				for(int k=0;k<16;k++){
					c= (int)in.read();
					FrameInfo[i].Name[k]=(char)c;
				}
				 
				 // Read MD2 style vertex data
				 //in.seek(Head.FrameOffset + (Head.FrameSize * i)+ _size of this shit up_);
				 for(int m = 0; m < nVtx; m++){
					 vtx[m] = new MD2Vtx();
					 for(int k=0;k<3;k++){
						 vtx[m].Vtx[k] = (int)in.read();
					 }
					 vtx[m].lNorm=(int)in.read(); 
				 }

				 // Convert vertices
				 for(int j = 0; j != nVtx; ++j){
					 frame[i].Vtx[j] = new Mesh_Vtx();
					 frame[i].Vtx[j].x=(vtx[j].Vtx[0] * FrameInfo[i].Scale[0])+FrameInfo[i].Translate[0];
					 frame[i].Vtx[j].y=(vtx[j].Vtx[1] * FrameInfo[i].Scale[0])+FrameInfo[i].Translate[1];   
					 frame[i].Vtx[j].z=(vtx[j].Vtx[2] * FrameInfo[i].Scale[0])+FrameInfo[i].Translate[2];
				}
			}
			
			// Calc normals for each frame
			for(int i= 0; i !=nFrames; i++){
				// Calc face normal
				for(int j = 0; j != nTri; j++){
					frame[i].Norm[j] = calcNormal(frame[i].Vtx[face[j].p1], frame[i].Vtx[face[j].p2], frame[i].Vtx[face[j].p3]);
				}
			}
		}catch(IOException e){
			e.printStackTrace();
			System.out.println("Error reading md2 file.");
		}
		
		
	}
	
	//Read all data form file
	//We dont check if this is real md2 file - version, id, length (todo)
	public void Load(String filename){
		try {
			in = new RandomAccessFile(filename,"rw");
			
			LoadHeader();
			//Head.printHeader();
			readShort();
			LoadFrames();
			
			in.close();
			
		} catch (FileNotFoundException e) {
			System.out.println("File "+filename+" not found.");
		} catch (IOException e) {
			System.out.println("Error reading md2 file.");
		}
	}
	
	public void Draw(int Frame){
		int part;
		
		//limit frame range
		if(Frame >= nFrames)
			Frame=0;
		

		
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, TexID);
		GL11.glBegin(GL11.GL_TRIANGLES);
		for(part=0; part < nTri; ++part){
			
			
			GL11.glNormal3f(frame[Frame].Norm[part].x, frame[Frame].Norm[part].y, frame[Frame].Norm[part].z);
			
			GL11.glTexCoord2f(UV[face[part].uv1].u, UV[face[part].uv1].v);
			GL11.glVertex3f(frame[Frame].Vtx[face[part].p1].x,frame[Frame].Vtx[face[part].p1].y,frame[Frame].Vtx[face[part].p1].z);
			
			GL11.glTexCoord2f(UV[face[part].uv2].u,UV[face[part].uv2].v);
			GL11.glVertex3f(frame[Frame].Vtx[face[part].p2].x,frame[Frame].Vtx[face[part].p2].y,frame[Frame].Vtx[face[part].p2].z);
			
			GL11.glTexCoord2f(UV[face[part].uv3].u,UV[face[part].uv3].v);
			GL11.glVertex3f(frame[Frame].Vtx[face[part].p3].x,frame[Frame].Vtx[face[part].p3].y,frame[Frame].Vtx[face[part].p3].z);
		}
		GL11.glEnd();
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
	}
	
	public int getFrameCount(){
		return nFrames;
	}
	
	public String getTexName(){
		return textureName;
	}
	
	public void setTexture(int TexNum){	//TexNum should be type GLuint
		TexID = TexNum;
	}
	
	public Mesh_Vtx calcNormal(Mesh_Vtx v1, Mesh_Vtx v2, Mesh_Vtx v3){
		  Mesh_Vtx Result = new Mesh_Vtx();
		  double v1x,v1y,v1z,v2x,v2y,v2z;
		  double nx,ny,nz;
		  double vLen;

		  // Calculate vectors
		  v1x = v1.x - v2.x;
		  v1y = v1.y - v2.y;
		  v1z = v1.z - v2.z;

		  v2x = v2.x - v3.x;
		  v2y = v2.y - v3.y;
		  v2z = v2.z - v3.z;
		  
		  // Get cross product of vectors

		  nx = (v1y * v2z) - (v1z * v2y);
		  ny = (v1z * v2x) - (v1x * v2z);
		  nz = (v1x * v2y) - (v1y * v2x);

		  // Normalise final vector
		  vLen = Math.sqrt( (nx * nx) + (ny * ny) + (nz * nz) );

		  Result.x = (float) (nx / vLen);
		  Result.y = (float) (ny / vLen);
		  Result.z = (float) (nz / vLen);
		  
		  return Result;
	}
	
	public int readInt() throws IOException{
		for(int i=0;i<4;i++)				
			b[i]=(byte)in.readUnsignedByte();
													//java uses big endian, but in .bsp files
		long l=(long)b[0] & 0xFF;					//the little endian is used
		l+=((long)b[1] & 0xFF) << 8;				//therefore the switch of an endian
		l+=((long)b[2] & 0xFF) << 16;				//must be performed
		l+=((long)b[3] & 0xFF) << 24;
		
		return (int)l;
	}
	
	public short readShort() throws IOException{
		for(int i=0;i<2;i++)				
			b[i]=(byte)in.readUnsignedByte();
		
		short returnNumber = (short)((b[0]) & 0xFF);
		returnNumber+=(short)((b[1]) & 0xFF) <<8;
		
		return returnNumber;
	}
	
	public float readFloat() throws IOException{
		for(int i=0;i<4;i++)				
			b[i]=(byte)in.readUnsignedByte();
		
		int i=(int)b[0] & 0xFF;
		i|=((int)b[1] & 0xFF) << 8;
		i|=((int)b[2] & 0xFF) << 16;
		i|=((int)b[3] & 0xFF) << 24;

		return Float.intBitsToFloat(i);
	}

	public void LoadModelAndTextures(String mapName){
		Load(mapName);
		
   	try{
   			//modify filename and set name of texture form .tga to .png
   			//we use newdawn library to import png texture
   			int offset = textureName.lastIndexOf("/");
   			textureName = textureName.substring(offset+1, textureName.lastIndexOf("."));
   			textureName = textureName + ".png";
   			Texture tex;
   			tex = TextureLoader.getTexture("PNG", new FileInputStream(textureName));
   			tex.bind();
   			TexID = tex.getTextureID();

    		GL11.glEnable(GL11.GL_DEPTH_TEST);
    		GL11.glEnable(GL11.GL_TEXTURE_2D);
    		
    		GL11.glTexParameteri(GL11.GL_TEXTURE_2D,GL11.GL_TEXTURE_MIN_FILTER,GL11.GL_LINEAR);
    		GL11.glTexParameteri(GL11.GL_TEXTURE_2D,GL11.GL_TEXTURE_MAG_FILTER,GL11.GL_LINEAR);
    		GL11.glTexParameteri(GL11.GL_TEXTURE_2D,GL11.GL_TEXTURE_WRAP_S,GL11.GL_CLAMP);
    		GL11.glTexParameteri(GL11.GL_TEXTURE_2D,GL11.GL_TEXTURE_WRAP_T,GL11.GL_CLAMP);

    	}catch(IOException e){
    		System.out.println("Error reading texture "+textureName);
    	}
    	
		
	}
		
	public void renderObject(int state){	//if state is 0 object is not moving
	    GL11.glPushMatrix();
	    
	    //set model velocity to 0, that we dont have sliding effect
	    model.getLinearVelocity(currentVelocity);
		model.setLinearVelocity(new Vector3f(0f,currentVelocity.y,0f));
	    // TRANSLATE 
		GL11.glTranslatef(m_nX, m_nY, m_nZ);
	    if (m_rZ!=0)
	      GL11.glRotatef(m_rZ, 0, 0, 1);
	    if (m_rY!=0)
	      GL11.glRotatef(m_rY, 0, 1, 0);
	    if (m_rX!=0)
	      GL11.glRotatef(m_rX, 1, 0, 0);
	    GL11.glScalef(m_sX, m_sY, m_sZ);
	    
	    //draw cube that represents physical model of object
	    //drawCube();	    

	    GL11.glTranslatef(0, -16f, 0);	//translate that model is there where cube is
	    GL11.glRotatef(-90f, 1f, 0f, 0f);
	    GL11.glRotatef(180f, 0f, 0f, 1f);	//rotate that you see back of object
	    GL11.glRotatef(90, 0, 0, 1);
	    
	    Time1 = System.nanoTime();

	    //System.out.println(CurrFrame+" "+nFrames);
	    //draw object
	    this.Draw(CurrFrame);
	    //advance frame counter if object is moving (state is 1)
	    if(state == 1){
		    if(Time1 > NextFrame){
		    	CurrFrame++;
		    	NextFrame = Time1 + FRAME_DELAY;
		    	if(CurrFrame >= nFrames)
		    		CurrFrame=0;
		    }
		    
		    
		    if(!footstepsSound.isAlive()){
		    	footstepsSound=new Sound("footsteps.wav",10);
		    }
		    try{	
		    	footstepsSound.start();
        		
        	}
        	catch(IllegalThreadStateException e){
        		//e.printStackTrace();
        	}
        	
	    }else if(state == 2){
		    if(Time1 > NextFrame){
		    	
		    	NextFrame = Time1 + FRAME_DELAY;
		    	if(CurrFrame == 0)
		    		CurrFrame=nFrames;
		    	CurrFrame--;
		    }
		    if(!footstepsSound.isAlive()){
		    	footstepsSound=new Sound("footsteps.wav",10);
		    }
		    try{	
		    	footstepsSound.start();
        		
        	}
        	catch(IllegalThreadStateException e){
        		//e.printStackTrace();
        	}

	    }

	    GL11.glTranslatef(-m_nX, -m_nY, -m_nZ);
	    GL11.glPopMatrix();
	}
		
	private void drawCube(){
	    //drawing cube
	    GL11.glBegin(GL11.GL_QUADS);
	    // width, height, length
	    GL11.glVertex3f( -o_width,  -o_height, -o_length);    // 1.spredaj
	    GL11.glVertex3f( -o_width,  o_height, -o_length);    // 2.
	    GL11.glVertex3f( o_width,  o_height, -o_length);    // 3.
	    GL11.glVertex3f( o_width,  -o_height, -o_length);    // 4.
	    
	    //GL11.glColor3f(1f, 1f, 0f);
	    GL11.glVertex3f( -o_width,  -o_height, -o_length);    // 1.
	    GL11.glVertex3f( o_width,  -o_height, -o_length);    // 2.
	    GL11.glVertex3f( o_width,  -o_height, o_length);    // 1.leva stranska
	    GL11.glVertex3f( -o_width,  -o_height, o_length);    // 2.
	    
	    //GL11.glColor3f(0f, 1f, 0f);
	    GL11.glVertex3f( -o_width,  -o_height, -o_length);    // 5.
	    GL11.glVertex3f( -o_width,  -o_height, o_length);    // 6.spredaj stranska
	    GL11.glVertex3f( -o_width,  o_height, o_length);    // 7.
	    GL11.glVertex3f( -o_width,  o_height, -o_length);    // 8.
	    
	    //GL11.glColor3f(0f, 0f, 1f);
	    GL11.glVertex3f( -o_width,  o_height, -o_length);    // 5. zadaj stranska
	    GL11.glVertex3f( -o_width,  o_height, o_length);    // 6.
	    GL11.glVertex3f( o_width,  o_height, o_length);    // 7.
	    GL11.glVertex3f( o_width,  o_height, -o_length);    // 8.
	    
	    //GL11.glColor3f(0f, 1f, 1f);
	    GL11.glVertex3f( o_width,  -o_height, o_length);    // 6.
	    GL11.glVertex3f( o_width,  -o_height, -o_length);    // 5.
	    GL11.glVertex3f( o_width,  o_height, -o_length);    // 7.
	    GL11.glVertex3f( o_width,  o_height, o_length);    // 8.
	    
	    //GL11.glColor3f(1f, 0f, 0f);
	    GL11.glVertex3f( -o_width,  -o_height, o_length);    // 5.zadaj
	    GL11.glVertex3f( o_width,  -o_height, o_length);    // 6.
	    GL11.glVertex3f( o_width,  o_height, o_length);    // 7.
	    GL11.glVertex3f( -o_width,  o_height, o_length);    // 8.
	    GL11.glEnd(); 	    
	}
		
	public void setPosition(float p_X, float p_Y, float p_Z){
	
	    m_nX=p_X; m_nY=p_Y; m_nZ=p_Z;
	    Transform t = new Transform();
	    t.origin.set(m_nX,m_nY,m_nZ);
		
	   this.model.setWorldTransform(t);
		
	}
	  
	public void setRotation(float p_X, float p_Y, float p_Z){
	    m_rX=p_X; m_rY=p_Y; m_rZ=p_Z;
	}
	  
	public void setScaling(float p_X, float p_Y, float p_Z){
	    m_sX=p_X; m_sY=p_Y; m_sZ=p_Z;
	}
	
	public RigidBody getPhyModel(){
		return model;
	}
	
	//supported structures
	protected class MD2Header{
	   int [] ID;			// File Type - Normally 'IPD2'
	   int Version;    
	   int TexWidth;   // Texture width
	   int TexHeight;  // Texture height 
	   int FrameSize;  // Size for frames in bytes
	   int nTextures;  // Number of textures
	   int nVertices;  // Number of vertices
	   int nTexCoords; // Number of UVs
	   int nTriangles; // Number of polys
	   int nGLCmd;     // Number of GL Commmands
	   int nFrames;    // Number of frames
	   int TexOffset;  // Offset to texture name(s)
	   int UVOffset;   // Offset to UV data
	   int FaceOffset; // Offset to poly data
	   int FrameOffset;// Offset to first frame  
	   int GLCmdOffset;// Offset to GL Cmds 
	   int EOFOffset;  // Size of file
	   
	   MD2Header(){
		   ID = new int[4];
	   }
	   public void printHeader(){
			String id="";
		   for (int i=0;i<4;i++){
				id +=(char)ID[i];
			}
			System.out.println("Id:"+id);
			System.out.println("Version:"+Version);
			System.out.println("TexWidth:"+TexWidth);
			System.out.println("TexHeight:"+TexHeight);
			System.out.println("FrameSize:"+FrameSize);
			System.out.println("nTextures:"+nTextures);
			System.out.println("nVertices:"+nVertices);
			System.out.println("nTexCoords:"+nTexCoords);
			System.out.println("nTriangles:"+nTriangles);
			System.out.println("nGLCmd:"+nGLCmd);
			System.out.println("nFrames:"+nFrames);
			System.out.println("TexOffset:"+TexOffset);
			System.out.println("UVOffset:"+UVOffset);
			System.out.println("FaceOffset:"+FaceOffset);
			System.out.println("FrameOffset:"+FrameOffset);
			System.out.println("GLCmdOffset:"+GLCmdOffset);
			System.out.println("EOFOffset:"+EOFOffset);
	   }
	}
	
	protected class MD2FrameInfo{
	  float[] Scale;
	  float[] Translate;
	  char [] Name;
	  
	  public MD2FrameInfo(){
		  Scale = new float[3];
		  Translate = new float[3];
		  Name = new char[16];
	  }
	}
	
	protected class MD2Face{
		short p1,p2,p3;
		short uv1,uv2,uv3;
	}
	
	protected class MD2Vtx{
	  int[] Vtx = new int[3];	//in c file is here both char
	  int lNorm;
	}
	
	protected class Mesh_Vtx{
		float x,y,z;
	}
	
	protected class Mesh_UV{
		float u,v;
	}
	
	protected class MD2Frame{
		Mesh_Vtx[] Vtx = new Mesh_Vtx[nVtx];
		Mesh_Vtx[] Norm = new Mesh_Vtx[nVtx];
	}
	
	protected class MD2TexCoord{
		short u,v;
	}

}
